/**
 * @license
 *
 * Grauw URI utilities
 *
 * See: http://hg.grauw.nl/grauw-lib/file/tip/src/uri.js
 *
 * @author Laurens Holst (http://www.grauw.nl/)
 *
 *   Copyright 2012 Laurens Holst
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

/**
 * Constructs a URI object.
 * @constructor
 * @class Implementation of URI parsing and base URI resolving algorithm in RFC 3986.
 * @param {string|URI} uri A string or URI object to create the object from.
 */
function URI(uri) {
	if (uri instanceof URI) {  // copy constructor
		this.scheme = uri.scheme;
		this.authority = uri.authority;
		this.path = uri.path;
		this.query = uri.query;
		this.fragment = uri.fragment;
	} else if (uri) {  // uri is URI string or cast to string
		var c = parseRegex.exec(uri);
		this.scheme = c[1];
		this.authority = c[2];
		this.path = c[3];
		this.query = c[4];
		this.fragment = c[5];
	}
}
// Initial values on the prototype
URI.prototype.scheme = null;
URI.prototype.authority = null;
URI.prototype.path = '';
URI.prototype.query = null;
URI.prototype.fragment = null;

// Regular expression from RFC 3986 appendix B
var parseRegex = new RegExp('^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\\?([^#]*))?(?:#(.*))?$');

/**
 * Returns the scheme part of the URI.
 * In "http://example.com:80/a/b?x#y" this is "http".
 */
URI.prototype.getScheme = function () {
	return this.scheme;
};

/**
 * Returns the authority part of the URI.
 * In "http://example.com:80/a/b?x#y" this is "example.com:80".
 */
URI.prototype.getAuthority = function () {
	return this.authority;
};

/**
 * Returns the path part of the URI.
 * In "http://example.com:80/a/b?x#y" this is "/a/b".
 * In "mailto:mike@example.com" this is "mike@example.com".
 */
URI.prototype.getPath = function () {
	return this.path;
};

/**
 * Returns the query part of the URI.
 * In "http://example.com:80/a/b?x#y" this is "x".
 */
URI.prototype.getQuery = function () {
	return this.query;
};

/**
 * Returns the fragment part of the URI.
 * In "http://example.com:80/a/b?x#y" this is "y".
 */
URI.prototype.getFragment = function () {
	return this.fragment;
};

/**
 * Tests whether the URI is an absolute URI.
 * See RFC 3986 section 4.3.
 */
URI.prototype.isAbsolute = function () {
	return !!this.scheme && !this.fragment;
};

///**
//* Extensive validation of the URI against the ABNF in RFC 3986
//*/
//URI.prototype.validate

/**
 * Tests whether the URI is a same-document reference.
 * See RFC 3986 section 4.4.
 *
 * To perform more thorough comparison, you can normalise the URI objects.
 */
URI.prototype.isSameDocumentAs = function (uri) {
	return uri.scheme == this.scheme &&
		uri.authority == this.authority &&
		uri.path == this.path &&
		uri.query == this.query;
};

/**
 * Simple String Comparison of two URIs.
 * See RFC 3986 section 6.2.1.
 *
 * To perform more thorough comparison, you can normalise the URI objects.
 */
URI.prototype.equals = function (uri) {
	return this.isSameDocumentAs(uri) && uri.fragment == this.fragment;
};

/**
 * Normalizes the URI using syntax-based normalization.
 * This includes case normalization, percent-encoding normalization and path segment normalization.
 * XXX: Percent-encoding normalization does not escape characters that need to be escaped.
 *      (Although that would not be a valid URI in the first place. See validate().)
 * See RFC 3986 section 6.2.2.
 */
URI.prototype.normalize = function () {
	this.removeDotSegments();
	if (this.scheme)
		this.scheme = this.scheme.toLowerCase();
	if (this.authority)
		this.authority = this.authority.replace(authorityRegex, replaceAuthority).
			replace(caseRegex, replaceCase);
	if (this.path)
		this.path = this.path.replace(caseRegex, replaceCase);
	if (this.query)
		this.query = this.query.replace(caseRegex, replaceCase);
	if (this.fragment)
		this.fragment = this.fragment.replace(caseRegex, replaceCase);
};

var caseRegex = /%[0-9a-z]{2}/gi;
var percentRegex = /[a-zA-Z0-9\-\._~]/;
var authorityRegex = /(.*@)?([^@:]*)(:.*)?/;

function replaceCase(str) {
	var dec = unescape(str);
	return percentRegex.test(dec) ? dec : str.toUpperCase();
}

function replaceAuthority(str, p1, p2, p3) {
	return (p1 || '') + p2.toLowerCase() + (p3 || '');
}

/**
 * Resolve a relative URI (this) against a base URI.
 * The base URI must be an absolute URI.
 * See RFC 3986 section 5.2
 */
URI.prototype.resolve = function (baseURI) {
	var uri = new URI();
	if (this.scheme) {
		uri.scheme = this.scheme;
		uri.authority = this.authority;
		uri.path = this.path;
		uri.query = this.query;
	} else {
		uri.scheme = baseURI.scheme;
		if (this.authority) {
			uri.authority = this.authority;
			uri.path = this.path;
			uri.query = this.query;
		} else {
			uri.authority = baseURI.authority;
			if (this.path == '') {
				uri.path = baseURI.path;
				uri.query = this.query || baseURI.query;
			} else {
				if (this.path.charAt(0) == '/') {
					uri.path = this.path;
					uri.removeDotSegments();
				} else {
					if (baseURI.authority && baseURI.path == '') {
						uri.path = '/' + this.path;
					} else {
						uri.path = baseURI.path.substring(0, baseURI.path.lastIndexOf('/') + 1) + this.path;
					}
					uri.removeDotSegments();
				}
				uri.query = this.query;
			}
		}
	}
	uri.fragment = this.fragment;
	return uri;
};

/**
 * Remove dot segments from path.
 * See RFC 3986 section 5.2.4
 * @private
 */
URI.prototype.removeDotSegments = function () {
	var input = this.path.split('/'),
		output = [],
		segment,
		absPath = input[0] == '';
	if (absPath)
		input.shift();
	var sFirst = input[0] == '' ? input.shift() : null;
	while (input.length) {
		segment = input.shift();
		if (segment == '..') {
			output.pop();
		} else if (segment != '.') {
			output.push(segment);
		}
	}
	if (segment == '.' || segment == '..')
		output.push('');
	if (absPath)
		output.unshift('');
	this.path = output.join('/');
};

// We don't like this function because it builds up a cache that is never cleared.
//	/**
//	 * Resolves a relative URI against an absolute base URI.
//	 * Convenience method.
//	 * @param {String} uri the relative URI to resolve
//	 * @param {String} baseURI the base URI (must be absolute) to resolve against
//	 */
//	URI.resolve = function(sURI, sBaseURI) {
//		var uri = cache[sURI] || (cache[sURI] = new URI(sURI));
//		var baseURI = cache[sBaseURI] || (cache[sBaseURI] = new URI(sBaseURI));
//		return uri.resolve(baseURI).toString();
//	};

//	var cache = {};

/**
 * Serialises the URI to a string.
 */
URI.prototype.toString = function () {
	var result = '';
	if (this.scheme)
		result += this.scheme + ':';
	if (this.authority)
		result += '//' + this.authority;
	result += this.path;
	if (this.query)
		result += '?' + this.query;
	if (this.fragment)
		result += '#' + this.fragment;
	return result;
};

module.exports = URI;

